/**CFile****************************************************************
 
  FileName    [app.c] 

  SystemName  []

  PackageName []
  
  Synopsis    [基于ABC的近似计算函数编写]

  Author      []
   
  Affiliation []

  Date        [2021.7.13]

***********************************************************************/
#include <iostream>
using namespace std;
#include <stdio.h>
#include <time.h>
#include "base/abc/abc.h"
#include "opt/cut/cut.h"
#include "opt/cut/cutInt.h"
#include "app.h"
#include "aig/aig/aig.h"
#include "simulatorPro.h"
#include "opt.h"
//static int count = 0;   //记录在saveMffc中被MffcCone引用的次数，作为结构体中MffcArray所存储的Mffc切割集的个数。

/*------------------------------------------------------------------------*/
/* 近似电路替换主程序                                                     */
/*------------------------------------------------------------------------*/
extern Abc_Ntk_t *App_Substitution(Abc_Ntk_t *pNtk, double bound, int errormodule)
{

  Abc_Ntk_t *pNtk_org = NULL, *ptemp = NULL;
  int tempNode = 0, templevel = 0, module = 0;
  double rate = 0, Lastrate = 0, finalrate = 0;
  //	*dMan = (AppManager *)calloc(sizeof(AppManager),sizeof(AppManager));
  //初始化
  //	AppManager_initial(*dMan, pNtk);
  //保存电路Mffc信息
  // AppSaveMffc(pNtk,*dMan);
  //打印基于MFFC分割的节点信息
  /*    for(i=0; i<count;i++)
  {
    for (int j=0; j<(*dMan)->MffcCone[i];j++)
    {
     printf("%d ",(*dMan)->MffcArray[i][j]);
    }
     printf("\n");
  }
  printf("\n");  */
  //复制原始电路
  ptemp = Abc_NtkDup(pNtk);
  pNtk_org = Abc_NtkDup(pNtk);
  Abc_NtkPrintStats(pNtk_org, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  pNtk_org = NtktoAig(pNtk_org);
  
    while (rate < bound)
    {
      random_device rd;
      unsigned seed = static_cast<unsigned>(rd());
      cout << "seed :" << seed << endl;
      /*  因为该算法会选出错误率约束下的最优解，最后一次优化一定会使得该次优化超过错误率
    也就是我们上一次获得的错误率就是我们面积最优近似下对于的最终错误率 */
      finalrate = Lastrate;
      tempNode = Abc_NtkNodeNum(pNtk);
      templevel = Abc_NtkLevel(pNtk);
      //ptemp用来保存之前一次近似的最优电路，如果下一次近似结果错误率大于设定值，则还原近似电路为ptemp
      Abc_NtkDelete(ptemp);
      ptemp = Abc_NtkDup(pNtk);
      //获取当前level下最优优化电路结构
      pNtk = GetBestNtk(pNtk, pNtk_org, Lastrate, bound, seed, module, errormodule);
      Abc_NtkPrintStats(pNtk, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
      
      cout << "node: " << Abc_NtkNodeNum(pNtk) << endl;
      //获得当前近似优化的错误率
      pNtk = NtktoAig(pNtk);
      if (!errormodule)
        Lastrate = MeasureER(pNtk, pNtk_org, 102400, seed, false);
      else
        Lastrate = MeasureRAEM(pNtk, pNtk_org, 102400, seed, false);
      pNtk = NtkStrash(pNtk);
      cout << "rate: " << Lastrate << endl;
      if (Lastrate > bound)
      {
        pNtk = ptemp;
        break;
      }
      //更新当前电路
      // pNtk = pNtk_app;
      //判断当前电路是否还存在多扇出节点，若不存在则退出循环
      if (tempNode == Abc_NtkNodeNum(pNtk))
        break;
    }
  cout << "final rate: " << finalrate << endl;
  Abc_NtkDelete(pNtk_org);
  //free_MffcArray(*dMan);
  // App_Free(*dMan);
  return pNtk;
}

/*------------------------------------------------------------------------*/
/* 初始化                                                                 */
/*------------------------------------------------------------------------*/
/* extern void AppManager_initial(AppManager *dMan, Abc_Ntk_t *pNtk)
{
  int nNodes = 0;
  nNodes = Abc_NtkObjNum( pNtk );   //读取电路网络有多少个节点从而开辟对应的空间
  dMan->nNodes = nNodes;
  dMan->MffcArray = (int **)calloc(sizeof(int)*nNodes,sizeof(int));
  dMan->MffcCone = (int *)calloc(sizeof(int)*nNodes,sizeof(int));
} */

Abc_Ntk_t *NtktoAig(Abc_Ntk_t *pNtk)
{
  Abc_Ntk_t *pNtkTemp = NULL;
  pNtk = Abc_NtkToLogic(pNtkTemp = pNtk);
  Abc_NtkDelete(pNtkTemp);
  Abc_NtkToAig(pNtk);
  return pNtk;
}
Abc_Ntk_t *NtkStrash(Abc_Ntk_t *pNtk)
{
  
  Abc_Ntk_t *pNtkTemp = NULL;
  pNtk = Abc_NtkStrash(pNtkTemp = pNtk, 0, 1, 0);
  Abc_NtkDelete(pNtkTemp);
  return pNtk;
}

/*------------------------------------------------------------------------*/
/* 复制网络程序                                                           */
/*------------------------------------------------------------------------*/
static Abc_Ntk_t *Ntk_Copy(Abc_Ntk_t *pNtk1)
{
  Abc_Ntk_t *pNtk_Copy = NULL;
  Abc_Obj_t *pObj = NULL;
  int i = 0;
  pNtk_Copy = Abc_NtkDup(pNtk1);
  pNtk1->pCopy = NULL;
  Abc_NtkForEachObj(pNtk1, pObj, i)
  {
    pObj->pCopy = NULL;
  }
  return pNtk_Copy;
}
/*------------------------------------------------------------------------*/
/* 获取所输入电路结构的MFFC节点                                          */
/*------------------------------------------------------------------------*/
/* void AppSaveMffc( Abc_Ntk_t * pNtk,AppManager *dMan)
{
    Abc_Obj_t * pNode;
    int i;
    Abc_NtkForEachNode( pNtk, pNode, i ){
        if ( Abc_ObjFanoutNum(pNode) > 1 )
        AppNodeMffcCone( pNode, dMan); 
        }
    printf("Mffc切割集个数:%d\n",count);
} */

/* void AppNodeMffcCone( Abc_Obj_t * pNode, AppManager *dMan)
{
    Vec_Ptr_t * vCone, * vSupp;
    Abc_Obj_t * pObj;
    int i=0;
    vCone = Vec_PtrAlloc( 100 );
    vSupp = Vec_PtrAlloc( 100 );
    Abc_NodeDeref_rec( pNode );
    Abc_NodeMffcConeSupp( pNode, vCone, vSupp );
    Abc_NodeRef_rec( pNode );
    //为MffcNodes申请长度为vCone大小的空间并清零
    dMan->MffcNodes = (int *)calloc(sizeof(int)*(vCone->nSize),sizeof(int));
    //获取Mffc切割集的根节点id
    dMan->MffcCone[count] = vCone->nSize;
    //将所获取的根节点所包含的子节点id记录到MffcNodes
    Vec_PtrForEachEntry( Abc_Obj_t *, vCone, pObj, i )
    {
      dMan->MffcNodes[i] = Abc_ObjId(pObj);
    }
    //将MffcNodes的值保存到MffcArray中 
    dMan->MffcArray[count] = dMan->MffcNodes;
    count++;
    Vec_PtrFree( vCone );
    Vec_PtrFree( vSupp );
}
 */
/*------------------------------------------------------------------------*/
/* 对多扇出节点拆分为对应扇出个数的子电路                                    */
/*------------------------------------------------------------------------*/

void AppNtksplit(Abc_Ntk_t *pNtk, Abc_Obj_t *pNode, int fanoutnum, int module)
{
  Abc_Obj_t *pConst1 = NULL, *pConst0 = NULL, *pCopy = NULL;
  Abc_Obj_t *pRootfanout = NULL;
  srand((int)time(0));
  if (fanoutnum > 100)
    return;
  else
  {
    if (fanoutnum > 1 && module == 1)
    {
      pRootfanout = Abc_ObjFanout(pNode, (rand() % fanoutnum));
      pCopy = AppNtkCreateObj(pNtk, ABC_OBJ_NODE);
      Abc_ObjPatchFanin(pRootfanout, pNode, pConst0);
      Apppreorder(pNtk, pCopy, pNode);
    }
    else
    {
      // pRootfanout =Abc_ObjFanout(pNode,(rand()%fanoutnum));
      pRootfanout = Abc_ObjFanout(pNode, 0);
      pConst1 = Abc_AigConst1(pNtk);
      pConst0 = Abc_ObjNot(pConst1);
      Abc_ObjPatchFanin(pRootfanout, pNode, pConst0);
    }
  }
}

/*------------------------------------------------------------------------*/
/* 通过递归获取复制电路结构并对输入PI置0                                     */
/*------------------------------------------------------------------------*/
void Apppreorder(Abc_Ntk_t *pNtk, Abc_Obj_t *pCopy, Abc_Obj_t *pNode)
{
  Abc_Obj_t *ptemp = NULL, *ptempNot = NULL;
  //判断当前节点的fanin0是否为PI，若是则将当前复制的节点与该节点的PI相连；
  //若不是则建立复制节点与下一个节点之间的连接关系并进行递归操作。
  if (!Abc_ObjIsPi(Abc_ObjFanin0(pNode)))
  {
    pCopy->fCompl0 = pNode->fCompl0;
    pCopy->left = AppNtkCreateObj(pNtk, ABC_OBJ_NODE);
    Abc_ObjAddFanin(pCopy, pCopy->left);
    pNode = Abc_ObjFanin0(pNode);
    Apppreorder(pNtk, pCopy->left, pNode);
  }
  else
  {
    pCopy->fCompl0 = pNode->fCompl0;
    ptemp = Abc_AigConst1(pNtk);
    ptempNot = Abc_ObjNot(ptemp);
    Abc_ObjAddFanin(pCopy, ptempNot);
  }
  //同上
  if (!Abc_ObjIsPi(Abc_ObjFanin1(pNode)))
  {
    pCopy->fCompl1 = pNode->fCompl1;
    pCopy->right = AppNtkCreateObj(pNtk, ABC_OBJ_NODE);
    Abc_ObjAddFanin(pCopy, pCopy->right);
    pNode = Abc_ObjFanin1(pNode);
    Apppreorder(pNtk, pCopy->right, pNode);
  }
  else
  {
    pCopy->fCompl1 = pNode->fCompl1;
    ptemp = Abc_AigConst1(pNtk);
    ptempNot = Abc_ObjNot(ptemp);
    Abc_ObjAddFanin(pCopy, ptempNot);
  }
}
/*------------------------------------------------------------------------*/
/* 追踪节点到PO的最长路径                                                 */
/*------------------------------------------------------------------------*/
int traceNodetoPo(Abc_Ntk_t *pNtk, Abc_Obj_t *pNode)
{
  Abc_Obj_t *ptemp;
  int fanoutSize = 0, num = 0, i = 0, j = 0, fanoutlevel = 0, Id = 0;
  //判断节点是否与PO相连的，若是返回0表示该节点为连接PO的节点
  if (pNode->Level == Abc_NtkLevel(pNtk))
    return 0;
  //通过for循环来寻找节点到PO的最短距离
  for (j = pNode->Level; j < Abc_NtkLevel(pNtk); j++)
  {
    fanoutlevel = pNode->Level;
    fanoutSize = pNode->vFanouts.nSize;
    //printf("%d %d ",fanoutSize,pNode->Id);
    //获取当前节点的扇出个数进行for循环，找到level最大的节点作为下一次循环的节点
    for (i = 0; i < fanoutSize; i++)
    {
      //判断当前节点的扇出是否连接到PO，若与PO相连，则直接返回根节点到当前节点的距离num
      if (Abc_ObjIsPo(Abc_ObjFanout(pNode, i)))
        return num;
      ptemp = Abc_ObjFanout(pNode, i);
      if (ptemp->Level > fanoutlevel)
      {
        fanoutlevel = ptemp->Level;
        Id = ptemp->Id;
      }
    }
    num++;
    pNode = Abc_NtkObj(pNtk, Id);
    // printf("%d ",pNode->Id);
    if (pNode->Level == Abc_NtkLevel(pNtk))
      break;
  }
  // printf("\n");
  return num;
}
/*------------------------------------------------------------------------*/
/* 获取当前电路level下的平均距离PO距离                                    */
/*------------------------------------------------------------------------*/

int GetAverageLevel(Abc_Ntk_t *pNtk, int level)
{
  Abc_Obj_t *pNode;
  int i = 0, num = 0, sum = 0;
  int average = 0, cont = 0;
  Abc_NtkForEachNode1(pNtk, pNode, i)
  {
    if (Abc_ObjFanoutNum(pNode) > 1 && pNode->Level == level)
    {
      num = traceNodetoPo(pNtk, pNode);
      sum = num + sum;
      cont++;
    }
  }
  //若当前节点没有多扇出节点，则返回0，防止sum/0报错
  if (cont == 0)
    return 0;
  if (sum == 0)
    return 0;
  average = sum / cont;
  return average;
}
/*------------------------------------------------------------------------*/
/* 获取优化效果最好的电路                                                */
/*------------------------------------------------------------------------*/
Abc_Ntk_t *GetBestNtk(Abc_Ntk_t *pNtk, Abc_Ntk_t *pNtk_org, double Lastrate, double bound, unsigned seed, int module, int errormodule)
{
  Abc_Obj_t *pNode = NULL;
  Abc_Ntk_t *pNtk_best = NULL, *pNtk_temp = NULL;
  int fanoutnum = 0, i = 0, j = 0, average = 0, NodeNum = 0, level = 0, tempNode = 0, templevel = 0, count = 0, weightcount = 0, ratecount = 0;
  double rate = 0, weight = 0, temp = 0, r = 0, bestrate = 0;
  pNtk_temp = Abc_NtkDup(pNtk);
  pNtk_best = Abc_NtkDup(pNtk);
  for (j = 1; j < Abc_NtkLevel(pNtk); j++)
  {
    if (Abc_NtkPoNum(pNtk) == 129 && Abc_NtkPiNum(pNtk) == 256)
      j = 3;
    if (Abc_NtkNodeNum(pNtk) < 300)
      average = 0;
    else
      average = GetAverageLevel(pNtk, j);
    if (average < 3)
    {
      average = 0;
    }
    if (count > 64)
      break;
    Abc_NtkForEachNode(pNtk, pNode, i)
    {
      level = traceNodetoPo(pNtk, pNode);
      if (Abc_ObjFanoutNum(pNode) > module && pNode->Level == j && level >= average)
      {
        count++;
        if (count > 64)
          break;
        /*         cout << "average :" << average << endl;
        cout << "level :" << j << endl; */
        // tempNode保存优化前节点个数，用来计算当前电路相较于原始电路优化了多少节点
        tempNode = Abc_NtkNodeNum(pNtk);
        /*       pMiter = NULL;
        pNtkRes = NULL; */
    //    Abc_NtkPrintStats( pNtk, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
        fanoutnum = pNode->vFanouts.nSize;
        //进行常量复制替换
        AppNtksplit(pNtk, pNode, fanoutnum, module);
        //电路优化
        pNtk = NtkStrash(pNtk);
        pNtk = Optimaize_resyn2(pNtk);
        pNtk = NtktoAig(pNtk);
        if (!errormodule)
          rate = MeasureER(pNtk, pNtk_org, 102400, seed, false);
        else
          rate = MeasureRAEM(pNtk, pNtk_org, 102400, seed, false);
        r = (rate - Lastrate);
        pNtk = NtkStrash(pNtk);
    //   Abc_NtkPrintStats( pNtk, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
    //     cout << "error rate :" << rate << endl;
        //NodeNum的值为当前电路同之前电路优化了多少个节点
        NodeNum = tempNode - Abc_NtkNodeNum(pNtk);
        //如果优化后的错误率比上一次优化过程要好，则返回本次优化电路
        if (rate - Lastrate < 0)
        {
          return pNtk;
        }
        //weight为权重值，用来作为我们选取最合适电路的衡量标准，它是用优化节点量除以本次优化与上一次优化的差值来计算得出
        weight = NodeNum / r;
        //判断常量替换后错误率同上一次的错误率是否相同且，并且判断电路节点是否减少，若是则返回该电路
        /* if(r==0&&NodeNum<3&&Abc_NtkNodeNum(pNtk)>2000)
          {
            Abc_NtkDelete(pNtk);
            pNtk = Abc_NtkDup(pNtk_temp);
            continue;
          } */
        if (r == 0 && NodeNum > 0)
        {
          templevel++;
          if (templevel == 3)
          {
            return pNtk_best;
          }
          else
            weight = 10000 * NodeNum;
        }
        if (rate < bound && Abc_NtkNodeNum(pNtk) > 5000 && NodeNum > 100)
          return pNtk;
        //判断当前错误率是否大于设定值，并且设定当前循环执行次数，若第一次执行就超过设定错误率，则直接返回当前电路
        if (rate > bound)
        {
          weightcount++;
          weight = -1;
          if (weightcount == 20)
          {
            weightcount = 0;
            break;
          }
        }
       //  cout << "weight :" << weight << endl;
        if (weight > temp)
        {
          ratecount++;
          temp = weight;
          Abc_NtkDelete(pNtk_best);
          bestrate = rate;
          pNtk_best = Abc_NtkDup(pNtk);
        }
        Abc_NtkDelete(pNtk);
        pNtk = Abc_NtkDup(pNtk_temp);
      }
    }
  }
  //   cout << "bestrate: " << bestrate << endl;
  Abc_NtkDelete(pNtk_temp);
  //  Abc_NtkPrintStats( pNtk_best, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
  return pNtk_best;
}

/*------------------------------------------------------------------------*/
/* 获取错误率                                                            */
/*------------------------------------------------------------------------*/

/* double GetErrorRate(Abc_Ntk_t * pNtk_app,Abc_Ntk_t * pNtk_org)
{
  Abc_Ntk_t *pMiter = NULL,*pNtkRes = NULL;
  double rate = 0;
  pMiter = Abc_AigCombine2Miter(pNtk_app, pNtk_org);
  pNtkRes = Abc_NtkCollapse( pMiter, ABC_INFINITY, 0, 1, 0, 0, 0 );
  ErrorRate(pNtkRes,NULL,&rate);
  Abc_NtkDelete(pMiter);
  Abc_NtkDelete(pNtkRes);
  return rate;
} */

/*------------------------------------------------------------------------*/
/* 创建新的节点                                                           */
/*------------------------------------------------------------------------*/
Abc_Obj_t *AppNtkCreateObj(Abc_Ntk_t *pNtk, Abc_ObjType_t Type)
{
  Abc_Obj_t *pObj;
  // create new object, assign ID, and add to the array
  pObj = Abc_ObjAlloc(pNtk, Type);
  pObj->Id = pNtk->vObjs->nSize;
  Vec_PtrPush(pNtk->vObjs, pObj);
  pNtk->nObjs++;
  //pObj->iTemp = Vec_PtrSize(pNtk->vCis);
  switch (Type)
  {
  case ABC_OBJ_PI:
    Vec_PtrPush(pNtk->vPis, pObj);
    Vec_PtrPush(pNtk->vCis, pObj);
    break;
  case ABC_OBJ_CONST1:
    assert(0);
    break;
  case ABC_OBJ_NODE:
    break;
  default:
    assert(0);
    break;
  }
  return pObj;
}